package com.star.security.context;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.star.security.authentication.AnonymousAuthenticationToken;
import com.star.security.authentication.Authentication;
import org.redisson.api.ExpiredObjectListener;
import org.redisson.api.RBucket;
import org.redisson.api.RSet;
import org.redisson.api.RedissonClient;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.util.concurrent.TimeUnit;

/**
 * Redisson令牌存储库
 *
 * @author yangzj
 */
public class RedissonTokenRepository implements TokenRepository {

    private final RedissonClient redisson;

    public RedissonTokenRepository(RedissonClient redisson) {
        this.redisson = redisson;
    }

    @Override
    public ResponseToken save(Authentication authentication) {
        String token = TokenBuilder.create();
        RBucket<Object> bucket = redisson.getBucket(TokenBuilder.createRedisKey(token));
        long expiresIn = authentication.getExpiresIn();
        bucket.set(authentication, expiresIn, TimeUnit.SECONDS);
        // 将同一个用户的token放到一个set中
        RSet<String> tokens = redisson.getSet(TokenBuilder.createRedisSetKey(authentication));
        // 判断一个用户是否可以多端登录
        if (!authentication.getMultiEndpoint()) {
            // 如果不行，删除之前的token
            tokens.forEach(c -> redisson.getBucket(TokenBuilder.createRedisKey(c)).delete());
            tokens.clear();
        }
        tokens.add(token);
        // 有效期时间后面的不一定比前面的大，保留最大
        long expireTime = tokens.getExpireTime();
        if (expireTime == -1) {
            // 首次创建=-1
            tokens.expire(Duration.ofSeconds(expiresIn));
        } else {
            tokens.expireIfGreater(Duration.ofSeconds(expiresIn));
        }
        // 监听 如果过期移除set里面的值
        bucket.addListener((ExpiredObjectListener) s -> tokens.remove(token));
        return new ResponseToken(token, expiresIn);
    }

    @Override
    public void refresh(Authentication authentication) {
        // 更新用户信息
        RSet<String> tokens = redisson.getSet(TokenBuilder.createRedisSetKey(authentication));
        tokens.forEach(token -> {
            RBucket<Object> bucket = redisson.getBucket(token);
            Authentication o = (Authentication) bucket.get();
            o.setPrincipal(authentication.getPrincipal());
            bucket.setAndKeepTTL(o);
        });
    }

    @Override
    public void remove(HttpServletRequest request) {
        // 移除当前用户的token
        String redisKey = TokenBuilder.createRedisKey(request);
        if (redisKey == null) {
            return;
        }
        RBucket<Object> bucket = redisson.getBucket(redisKey);
        Authentication authentication = (Authentication) bucket.get();
        // 移除set列表里面当前用户的token
        RSet<String> tokens = redisson.getSet(TokenBuilder.createRedisSetKey(authentication));
        tokens.remove(TokenBuilder.getValue(request));
        // 再移除token
        bucket.delete();

    }

    @Override
    public void remove(Authentication authentication) {
        // 删除该用户所有token
        RSet<String> tokens = redisson.getSet(TokenBuilder.createRedisSetKey(authentication));
        tokens.forEach(token -> redisson.getBucket(TokenBuilder.createRedisKey(token)).delete());
        tokens.delete();
    }

    @Override
    public Authentication get(HttpServletRequest request) {
        String redisKey = TokenBuilder.createRedisKey(request);
        if (redisKey == null) {
            return AnonymousAuthenticationToken.unauthenticated();
        }
        RBucket<Object> bucket = redisson.getBucket(redisKey);
        return (Authentication) bucket.get();
    }

    /**
     * 访问令牌生成器
     *
     * @author yangzj
     */
    static class TokenBuilder {
        /**
         * 创造
         *
         * @return {@link String}
         */
        public static String create() {
            return IdUtil.fastSimpleUUID();
        }

        /**
         * 创建redis key
         *
         * @param token 访问令牌
         * @return {@link String}
         */
        public static String createRedisKey(String token) {
            if (token == null) {
                return null;
            }
            return TOKEN + ":value:" + token;
        }

        /**
         * 创建redis key
         *
         * @param request HttpServletRequest
         * @return {@link String}
         */
        public static String createRedisKey(HttpServletRequest request) {
            return createRedisKey(getValue(request));
        }

        /**
         * 创建redis set key-用户对应的token列表
         *
         * @param authentication 身份验证
         * @return {@link String}
         */
        public static String createRedisSetKey(Authentication authentication) {
            return TOKEN + ":name:" + authentication.getName();
        }

        /**
         * 获取token值
         * -1.从cookie中获取
         * -2.从header中获取
         * -3.从请求参数中获取 ?access_token=xxx
         *
         * @param request HttpServletRequest
         * @return {@link String}
         */
        public static String getValue(HttpServletRequest request) {
            Cookie[] cookies = request.getCookies();
            if (cookies != null) {
                for (Cookie cookie : cookies) {
                    if (TOKEN.equals(cookie.getName())) {
                        return cookie.getValue();
                    }
                }
            }
            String authorization = request.getHeader("Authorization");
            if (StrUtil.isNotBlank(authorization) && authorization.startsWith("Bearer ")) {
                return authorization.substring(7);
            }
            return request.getParameter(TOKEN);
        }
    }
}
